// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/ppapi_plugin/ppapi_thread.h"

#include <stddef.h>

#include <limits>

#include "base/command_line.h"
#include "base/cpu.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/discardable_memory_allocator.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
#include "content/child/browser_font_resource_trusted.h"
#include "content/child/child_process.h"
#include "content/common/child_process_messages.h"
#include "content/ppapi_plugin/broker_process_dispatcher.h"
#include "content/ppapi_plugin/plugin_process_dispatcher.h"
#include "content/ppapi_plugin/ppapi_blink_platform_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/pepper_plugin_info.h"
#include "content/public/common/sandbox_init.h"
#include "content/public/common/service_manager_connection.h"
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_platform_file.h"
#include "ipc/ipc_sync_channel.h"
#include "ipc/ipc_sync_message_filter.h"
#include "ppapi/c/dev/ppp_network_state_dev.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppp.h"
#include "ppapi/proxy/interface_list.h"
#include "ppapi/proxy/plugin_globals.h"
#include "ppapi/proxy/plugin_message_filter.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/resource_reply_thread_registrar.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "services/ui/public/interfaces/constants.mojom.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "ui/base/ui_base_switches.h"

#if defined(OS_WIN)
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "content/child/font_warmup_win.h"
#include "sandbox/win/src/sandbox.h"
#elif defined(OS_MACOSX)
#include "content/common/sandbox_init_mac.h"
#endif

#if defined(OS_WIN)
const char kWidevineCdmAdapterFileName[] = "widevinecdmadapter.dll";

extern sandbox::TargetServices* g_target_services;

// Used by EnumSystemLocales for warming up.
static BOOL CALLBACK EnumLocalesProc(LPTSTR lpLocaleString)
{
    return TRUE;
}

static BOOL CALLBACK EnumLocalesProcEx(
    LPWSTR lpLocaleString,
    DWORD dwFlags,
    LPARAM lParam)
{
    return TRUE;
}

// Warm up language subsystems before the sandbox is turned on.
static void WarmupWindowsLocales(const ppapi::PpapiPermissions& permissions)
{
    ::GetUserDefaultLangID();
    ::GetUserDefaultLCID();

    if (permissions.HasPermission(ppapi::PERMISSION_FLASH)) {
        if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
            typedef BOOL(WINAPI * PfnEnumSystemLocalesEx)(LOCALE_ENUMPROCEX, DWORD, LPARAM, LPVOID);

            HMODULE handle_kern32 = GetModuleHandleW(L"Kernel32.dll");
            PfnEnumSystemLocalesEx enum_sys_locales_ex = reinterpret_cast<PfnEnumSystemLocalesEx>(GetProcAddress(handle_kern32, "EnumSystemLocalesEx"));

            enum_sys_locales_ex(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0);
        } else {
            EnumSystemLocalesW(EnumLocalesProc, LCID_INSTALLED);
        }
    }
}

#endif

static bool IsRunningInMash()
{
    const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
    return cmdline->HasSwitch(switches::kIsRunningInMash);
}

namespace content {

typedef int32_t (*InitializeBrokerFunc)(PP_ConnectInstance_Func* connect_instance_func);

PpapiThread::PpapiThread(const base::CommandLine& command_line, bool is_broker)
    : is_broker_(is_broker)
    , plugin_globals_(GetIOTaskRunner())
    , connect_instance_func_(NULL)
    , local_pp_module_(base::RandInt(0, std::numeric_limits<PP_Module>::max()))
    , next_plugin_dispatcher_id_(1)
    , field_trial_syncer_(this)
{
    plugin_globals_.SetPluginProxyDelegate(this);
    plugin_globals_.set_command_line(
        command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs));

    blink_platform_impl_.reset(new PpapiBlinkPlatformImpl);
    blink::Platform::initialize(blink_platform_impl_.get());

    if (!is_broker_) {
        scoped_refptr<ppapi::proxy::PluginMessageFilter> plugin_filter(
            new ppapi::proxy::PluginMessageFilter(
                NULL, plugin_globals_.resource_reply_thread_registrar()));
        channel()->AddFilter(plugin_filter.get());
        plugin_globals_.RegisterResourceMessageFilters(plugin_filter.get());
    }

    // In single process, browser main loop set up the discardable memory
    // allocator.
    if (!command_line.HasSwitch(switches::kSingleProcess)) {
        discardable_memory::mojom::DiscardableSharedMemoryManagerPtr manager_ptr;
        if (IsRunningInMash()) {
#if defined(USE_AURA)
            GetServiceManagerConnection()->GetConnector()->BindInterface(
                ui::mojom::kServiceName, &manager_ptr);
#else
            NOTREACHED();
#endif
        } else {
            ChildThread::Get()->GetRemoteInterfaces()->GetInterface(
                mojo::MakeRequest(&manager_ptr));
        }
        discardable_shared_memory_manager_ = base::MakeUnique<
            discardable_memory::ClientDiscardableSharedMemoryManager>(
            std::move(manager_ptr), GetIOTaskRunner());
        base::DiscardableMemoryAllocator::SetInstance(
            discardable_shared_memory_manager_.get());
    }
    field_trial_syncer_.InitFieldTrialObserving(command_line,
        switches::kSingleProcess);
}

PpapiThread::~PpapiThread()
{
}

void PpapiThread::Shutdown()
{
    ChildThreadImpl::Shutdown();

    ppapi::proxy::PluginGlobals::Get()->ResetPluginProxyDelegate();
    if (plugin_entry_points_.shutdown_module)
        plugin_entry_points_.shutdown_module();
    blink_platform_impl_->Shutdown();
}

bool PpapiThread::Send(IPC::Message* msg)
{
    // Allow access from multiple threads.
    if (message_loop()->task_runner()->BelongsToCurrentThread())
        return ChildThreadImpl::Send(msg);

    return sync_message_filter()->Send(msg);
}

// Note that this function is called only for messages from the channel to the
// browser process. Messages from the renderer process are sent via a different
// channel that ends up at Dispatcher::OnMessageReceived.
bool PpapiThread::OnControlMessageReceived(const IPC::Message& msg)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(PpapiThread, msg)
    IPC_MESSAGE_HANDLER(PpapiMsg_LoadPlugin, OnLoadPlugin)
    IPC_MESSAGE_HANDLER(PpapiMsg_CreateChannel, OnCreateChannel)
    IPC_MESSAGE_HANDLER(PpapiMsg_SetNetworkState, OnSetNetworkState)
    IPC_MESSAGE_HANDLER(PpapiMsg_Crash, OnCrash)
    IPC_MESSAGE_HANDLER(PpapiMsg_Hang, OnHang)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

void PpapiThread::OnChannelConnected(int32_t peer_pid)
{
    ChildThreadImpl::OnChannelConnected(peer_pid);
#if defined(OS_WIN)
    if (is_broker_)
        peer_handle_.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, peer_pid));
#endif
}

base::SingleThreadTaskRunner* PpapiThread::GetIPCTaskRunner()
{
    return ChildProcess::current()->io_task_runner();
}

base::WaitableEvent* PpapiThread::GetShutdownEvent()
{
    return ChildProcess::current()->GetShutDownEvent();
}

IPC::PlatformFileForTransit PpapiThread::ShareHandleWithRemote(
    base::PlatformFile handle,
    base::ProcessId peer_pid,
    bool should_close_source)
{
    return IPC::GetPlatformFileForTransit(handle, should_close_source);
}

base::SharedMemoryHandle PpapiThread::ShareSharedMemoryHandleWithRemote(
    const base::SharedMemoryHandle& handle,
    base::ProcessId remote_pid)
{
    DCHECK(remote_pid != base::kNullProcessId);
    return base::SharedMemory::DuplicateHandle(handle);
}

std::set<PP_Instance>* PpapiThread::GetGloballySeenInstanceIDSet()
{
    return &globally_seen_instance_ids_;
}

IPC::Sender* PpapiThread::GetBrowserSender()
{
    return this;
}

std::string PpapiThread::GetUILanguage()
{
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    return command_line->GetSwitchValueASCII(switches::kLang);
}

void PpapiThread::PreCacheFontForFlash(const void* logfontw)
{
#if defined(OS_WIN)
    ChildThreadImpl::PreCacheFont(*static_cast<const LOGFONTW*>(logfontw));
#endif
}

void PpapiThread::SetActiveURL(const std::string& url)
{
    GetContentClient()->SetActiveURL(GURL(url));
}

PP_Resource PpapiThread::CreateBrowserFont(
    ppapi::proxy::Connection connection,
    PP_Instance instance,
    const PP_BrowserFont_Trusted_Description& desc,
    const ppapi::Preferences& prefs)
{
    if (!BrowserFontResource_Trusted::IsPPFontDescriptionValid(desc))
        return 0;
    return (new BrowserFontResource_Trusted(
                connection, instance, desc, prefs))
        ->GetReference();
}

uint32_t PpapiThread::Register(
    ppapi::proxy::PluginDispatcher* plugin_dispatcher)
{
    if (!plugin_dispatcher || plugin_dispatchers_.size() >= std::numeric_limits<uint32_t>::max()) {
        return 0;
    }

    uint32_t id = 0;
    do {
        // Although it is unlikely, make sure that we won't cause any trouble when
        // the counter overflows.
        id = next_plugin_dispatcher_id_++;
    } while (id == 0 || plugin_dispatchers_.find(id) != plugin_dispatchers_.end());
    plugin_dispatchers_[id] = plugin_dispatcher;
    return id;
}

void PpapiThread::Unregister(uint32_t plugin_dispatcher_id)
{
    plugin_dispatchers_.erase(plugin_dispatcher_id);
}

void PpapiThread::OnLoadPlugin(const base::FilePath& path,
    const ppapi::PpapiPermissions& permissions)
{
    // In case of crashes, the crash dump doesn't indicate which plugin
    // it came from.
    base::debug::SetCrashKeyValue("ppapi_path", path.MaybeAsASCII());

    SavePluginName(path);

    // This must be set before calling into the plugin so it can get the
    // interfaces it has permission for.
    ppapi::proxy::InterfaceList::SetProcessGlobalPermissions(permissions);
    permissions_ = permissions;

    // Trusted Pepper plugins may be "internal", i.e. built-in to the browser
    // binary.  If we're being asked to load such a plugin (e.g. the Chromoting
    // client) then fetch the entry points from the embedder, rather than a DLL.
    std::vector<PepperPluginInfo> plugins;
    GetContentClient()->AddPepperPlugins(&plugins);
    for (size_t i = 0; i < plugins.size(); ++i) {
        if (plugins[i].is_internal && plugins[i].path == path) {
            // An internal plugin is being loaded, so fetch the entry points.
            plugin_entry_points_ = plugins[i].internal_entry_points;
        }
    }

    // If the plugin isn't internal then load it from |path|.
    base::ScopedNativeLibrary library;
    if (plugin_entry_points_.initialize_module == NULL) {
        // Load the plugin from the specified library.
        base::NativeLibraryLoadError error;
        base::TimeDelta load_time;
        {
            TRACE_EVENT1("ppapi", "PpapiThread::LoadPlugin", "path",
                path.MaybeAsASCII());

            base::TimeTicks start = base::TimeTicks::Now();
            library.Reset(base::LoadNativeLibrary(path, &error));
            load_time = base::TimeTicks::Now() - start;
        }

        if (!library.is_valid()) {
            LOG(ERROR) << "Failed to load Pepper module from " << path.value()
                       << " (error: " << error.ToString() << ")";
            if (!base::PathExists(path)) {
                ReportLoadResult(path, FILE_MISSING);
                return;
            }
            ReportLoadResult(path, LOAD_FAILED);
            // Report detailed reason for load failure.
            ReportLoadErrorCode(path, error);
            return;
        }

        // Only report load time for success loads.
        ReportLoadTime(path, load_time);

        // Get the GetInterface function (required).
        plugin_entry_points_.get_interface = reinterpret_cast<PP_GetInterface_Func>(
            library.GetFunctionPointer("PPP_GetInterface"));
        if (!plugin_entry_points_.get_interface) {
            LOG(WARNING) << "No PPP_GetInterface in plugin library";
            ReportLoadResult(path, ENTRY_POINT_MISSING);
            return;
        }

        // The ShutdownModule/ShutdownBroker function is optional.
        plugin_entry_points_.shutdown_module = is_broker_ ? reinterpret_cast<PP_ShutdownModule_Func>(
                                                   library.GetFunctionPointer("PPP_ShutdownBroker"))
                                                          : reinterpret_cast<PP_ShutdownModule_Func>(
                                                              library.GetFunctionPointer("PPP_ShutdownModule"));

        if (!is_broker_) {
            // Get the InitializeModule function (required for non-broker code).
            plugin_entry_points_.initialize_module = reinterpret_cast<PP_InitializeModule_Func>(
                library.GetFunctionPointer("PPP_InitializeModule"));
            if (!plugin_entry_points_.initialize_module) {
                LOG(WARNING) << "No PPP_InitializeModule in plugin library";
                ReportLoadResult(path, ENTRY_POINT_MISSING);
                return;
            }
        }
    }

#if defined(OS_WIN)
    // If code subsequently tries to exit using abort(), force a crash (since
    // otherwise these would be silent terminations and fly under the radar).
    base::win::SetAbortBehaviorForCrashReporting();

    // Once we lower the token the sandbox is locked down and no new modules
    // can be loaded. TODO(cpu): consider changing to the loading style of
    // regular plugins.
    if (g_target_services) {
        // Let Flash and Widevine CDM adapter load DXVA before lockdown on Vista+.
        if (permissions.HasPermission(ppapi::PERMISSION_FLASH) || path.BaseName().MaybeAsASCII() == kWidevineCdmAdapterFileName) {
            if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) {
                LoadLibraryA("dxva2.dll");
            }
        }

        if (permissions.HasPermission(ppapi::PERMISSION_FLASH)) {
            if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_WIN7) {
                base::CPU cpu;
                if (cpu.vendor_name() == "AuthenticAMD") {
                    // The AMD crypto acceleration is only AMD Bulldozer and above.
#if defined(_WIN64)
                    LoadLibraryA("amdhcp64.dll");
#else
                    LoadLibraryA("amdhcp32.dll");
#endif
                }
            }
        }

        // Cause advapi32 to load before the sandbox is turned on.
        unsigned int dummy_rand;
        rand_s(&dummy_rand);

        WarmupWindowsLocales(permissions);

        if (!base::win::IsUser32AndGdi32Available() && permissions.HasPermission(ppapi::PERMISSION_FLASH)) {
            PatchGdiFontEnumeration(path);
        }

        g_target_services->LowerToken();
    }
#endif

    if (is_broker_) {
        // Get the InitializeBroker function (required).
        InitializeBrokerFunc init_broker = reinterpret_cast<InitializeBrokerFunc>(
            library.GetFunctionPointer("PPP_InitializeBroker"));
        if (!init_broker) {
            LOG(WARNING) << "No PPP_InitializeBroker in plugin library";
            ReportLoadResult(path, ENTRY_POINT_MISSING);
            return;
        }

        int32_t init_error = init_broker(&connect_instance_func_);
        if (init_error != PP_OK) {
            LOG(WARNING) << "InitBroker failed with error " << init_error;
            ReportLoadResult(path, INIT_FAILED);
            return;
        }
        if (!connect_instance_func_) {
            LOG(WARNING) << "InitBroker did not provide PP_ConnectInstance_Func";
            ReportLoadResult(path, INIT_FAILED);
            return;
        }
    } else {
#if defined(OS_MACOSX)
        // We need to do this after getting |PPP_GetInterface()| (or presumably
        // doing something nontrivial with the library), else the sandbox
        // intercedes.
        CHECK(InitializeSandbox());
#endif

        int32_t init_error = plugin_entry_points_.initialize_module(
            local_pp_module_,
            &ppapi::proxy::PluginDispatcher::GetBrowserInterface);
        if (init_error != PP_OK) {
            LOG(WARNING) << "InitModule failed with error " << init_error;
            ReportLoadResult(path, INIT_FAILED);
            return;
        }
    }

    // Initialization succeeded, so keep the plugin DLL loaded.
    library_.Reset(library.Release());

    ReportLoadResult(path, LOAD_SUCCESS);
}

void PpapiThread::OnCreateChannel(base::ProcessId renderer_pid,
    int renderer_child_id,
    bool incognito)
{
    IPC::ChannelHandle channel_handle;

    if (!plugin_entry_points_.get_interface || // Plugin couldn't be loaded.
        !SetupChannel(renderer_pid, renderer_child_id, incognito,
            &channel_handle)) {
        Send(new PpapiHostMsg_ChannelCreated(IPC::ChannelHandle()));
        return;
    }

    Send(new PpapiHostMsg_ChannelCreated(channel_handle));
}

void PpapiThread::OnSetNetworkState(bool online)
{
    // Note the browser-process side shouldn't send us these messages in the
    // first unless the plugin has dev permissions, so we don't need to check
    // again here. We don't want random plugins depending on this dev interface.
    if (!plugin_entry_points_.get_interface)
        return;
    const PPP_NetworkState_Dev* ns = static_cast<const PPP_NetworkState_Dev*>(
        plugin_entry_points_.get_interface(PPP_NETWORK_STATE_DEV_INTERFACE));
    if (ns)
        ns->SetOnLine(PP_FromBool(online));
}

void PpapiThread::OnCrash()
{
    // Intentionally crash upon the request of the browser.
    //
    // Linker's ICF feature may merge this function with other functions with the
    // same definition and it may confuse the crash report processing system.
    static int static_variable_to_make_this_function_unique = 0;
    base::debug::Alias(&static_variable_to_make_this_function_unique);

    volatile int* null_pointer = nullptr;
    *null_pointer = 0;
}

void PpapiThread::OnHang()
{
    // Intentionally hang upon the request of the browser.
    for (;;)
        base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
}

void PpapiThread::OnFieldTrialGroupFinalized(const std::string& trial_name,
    const std::string& group_name)
{
    // IPC to the browser process to tell it the specified trial was activated.
    Send(new PpapiHostMsg_FieldTrialActivated(trial_name));
}

bool PpapiThread::SetupChannel(base::ProcessId renderer_pid,
    int renderer_child_id,
    bool incognito,
    IPC::ChannelHandle* handle)
{
    DCHECK(is_broker_ == (connect_instance_func_ != NULL));
    mojo::MessagePipe pipe;

    ppapi::proxy::ProxyChannel* dispatcher = NULL;
    bool init_result = false;
    if (is_broker_) {
        bool peer_is_browser = renderer_pid == base::kNullProcessId;
        BrokerProcessDispatcher* broker_dispatcher = new BrokerProcessDispatcher(plugin_entry_points_.get_interface,
            connect_instance_func_, peer_is_browser);
        init_result = broker_dispatcher->InitBrokerWithChannel(
            this, renderer_pid, pipe.handle0.release(), false);
        dispatcher = broker_dispatcher;
    } else {
        DCHECK_NE(base::kNullProcessId, renderer_pid);
        PluginProcessDispatcher* plugin_dispatcher = new PluginProcessDispatcher(plugin_entry_points_.get_interface,
            permissions_,
            incognito);
        init_result = plugin_dispatcher->InitPluginWithChannel(
            this, renderer_pid, pipe.handle0.release(), false);
        dispatcher = plugin_dispatcher;
    }

    if (!init_result) {
        delete dispatcher;
        return false;
    }
    *handle = pipe.handle1.release();

    // From here, the dispatcher will manage its own lifetime according to the
    // lifetime of the attached channel.
    return true;
}

void PpapiThread::SavePluginName(const base::FilePath& path)
{
    ppapi::proxy::PluginGlobals::Get()->set_plugin_name(
        path.BaseName().AsUTF8Unsafe());
}

static std::string GetHistogramName(bool is_broker,
    const std::string& metric_name,
    const base::FilePath& path)
{
    return std::string("Plugin.Ppapi") + (is_broker ? "Broker" : "Plugin") + metric_name + "_" + path.BaseName().MaybeAsASCII();
}

void PpapiThread::ReportLoadResult(const base::FilePath& path,
    LoadResult result)
{
    DCHECK_LT(result, LOAD_RESULT_MAX);

    // Note: This leaks memory, which is expected behavior.
    base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
        GetHistogramName(is_broker_, "LoadResult", path),
        1,
        LOAD_RESULT_MAX,
        LOAD_RESULT_MAX + 1,
        base::HistogramBase::kUmaTargetedHistogramFlag);

    histogram->Add(result);
}

void PpapiThread::ReportLoadErrorCode(
    const base::FilePath& path,
    const base::NativeLibraryLoadError& error)
{
// Only report load error code on Windows because that's the only platform that
// has a numerical error value.
#if defined(OS_WIN)
    // For sparse histograms, we can use the macro, as it does not incorporate a
    // static.
    UMA_HISTOGRAM_SPARSE_SLOWLY(
        GetHistogramName(is_broker_, "LoadErrorCode", path), error.code);
#endif
}

void PpapiThread::ReportLoadTime(const base::FilePath& path,
    const base::TimeDelta load_time)
{
    // Note: This leaks memory, which is expected behavior.
    base::HistogramBase* histogram = base::Histogram::FactoryTimeGet(
        GetHistogramName(is_broker_, "LoadTime", path),
        base::TimeDelta::FromMilliseconds(1),
        base::TimeDelta::FromSeconds(10),
        50,
        base::HistogramBase::kUmaTargetedHistogramFlag);

    histogram->AddTime(load_time);
}

} // namespace content
